﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

using FileCopy.Common;

namespace FileCopy.Sender
{
    class Program
    {
        [STAThread] // Required to be able to access the clipboard.
        static void Main(string[] args)
        {
            // Display help if no args are present.
            string applicationFileName = Assembly.GetExecutingAssembly().FullName;
            if (args.Length == 0)
            {
                Console.WriteLine(String.Format("{0} <file>", applicationFileName));
                Console.WriteLine(String.Format("{0} <file> <maxbytes>", applicationFileName));
                return;
            }

            // Validation.
            if (String.IsNullOrEmpty(args[0]))
            {
                Console.WriteLine("File must be specified.");
                return;
            }

            FileInfo file;
            try
            {
                file = new FileInfo(args[0]);
            }
            catch
            {
                Console.WriteLine("Invalid file.");
                return;
            }

            if (!file.Exists)
            {
                Console.WriteLine("Cannot find file.");
                return;
            }

            int maxClipboardBufferSize = 1024;
            if (args.Length >= 2 && !String.IsNullOrEmpty(args[1]))
            {
                try
                {
                    maxClipboardBufferSize = int.Parse(args[1]);
                }
                catch
                {
                    Console.WriteLine("Max bytes is invalid. Please enter a number or leave it blank.");
                }
            }

            // Initial messages showing the settings.
            Console.WriteLine(String.Format("File: {0}", file.Name));
            Console.WriteLine(String.Format("Max clipboard buffer size: {0}", maxClipboardBufferSize));
            Console.WriteLine();
            Console.WriteLine();

            // Open the file.            
            using (FileStream fileStream = file.OpenRead())
            {
                // Notify the listener of the file name and length. Wait for acknowledgement.
                string dataForClipboard = fileStream.Length.ToString();
                string key = file.Name;
                Console.WriteLine(String.Format("Opened file and waiting for acknowledgement. File: {0}. Size: {1}", key, dataForClipboard));
                SendCommandAndWaitForAcknowledgement(Command.Build(dataForClipboard, key, CommandType.NewFile));

                // Send the contents.
                do
                {
#if (DEBUG)
                    Console.WriteLine("  Reading content to send.");
#endif

                    StringBuilder buffer = new StringBuilder();
                    int count = 0;
                    do
                    {
                        int data = fileStream.ReadByte();
                        if (data == -1)
                        {
                            break;
                        }

                        buffer.AppendFormat(String.Format("{0},", data));
                        count++;
                    }
                    while (count < maxClipboardBufferSize);
                    dataForClipboard = buffer.ToString().TrimEnd(',');

                    if (String.IsNullOrEmpty(dataForClipboard))
                    {
                        break;
                    }

                    key = String.Format("{0}:{1}", fileStream.Position.ToString(), dataForClipboard.GetHashCode());
                    decimal progress = (Decimal)fileStream.Position / (Decimal)fileStream.Length;
                    Console.WriteLine(String.Format("Sending content. Progress: {1}. Key: {2}. Data Length: {0}", dataForClipboard.Length, progress.ToString("00.00%"), key));
                    SendCommandAndWaitForAcknowledgement(Command.Build(dataForClipboard, key, CommandType.FileContent));
                }
                while (true);

                // Let the listener know that the end of the file is reached.
                key = file.Name;
                dataForClipboard = Common.File.GetHash(file.FullName).ToString();
                Console.WriteLine(String.Format("Sending end of file command. File: {0}", key));
                SendCommandAndWaitForAcknowledgement(Command.Build(dataForClipboard, key, CommandType.EndOfFile));
            }
        }

        private static void SendCommandAndWaitForAcknowledgement(Command.CommandObject command)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            // Send command.
            string expectedAcknowledgement = Command.GetAcknowledgement(command.Key, command.Type);
            string resendCommand = Command.Build(null, command.Key, CommandType.Resend).ToString();

#if (DEBUG)
            Console.WriteLine(String.Format("  Sending command. Time: {0}ms", stopwatch.ElapsedMilliseconds));
#endif
            FileCopy.Common.Clipboard.SetText(command.ToString());

#if (DEBUG)
            Console.WriteLine(String.Format("  Command sent. Time: {0}ms", stopwatch.ElapsedMilliseconds));
#endif

            // Wait for the acknowledgement.
            do
            {
                string currentData = Clipboard.WaitForClipboardToChange(command.ToString(), 10000);

                if (String.Compare(currentData, expectedAcknowledgement) == 0)
                {
#if (DEBUG)
                    Console.WriteLine(String.Format("  Acknowledgement received. Time: {0}ms", stopwatch.ElapsedMilliseconds));
#endif
                    return;
                }

#if (DEBUG)
                Console.WriteLine(String.Format("  Waiting for acknowledgement. Time: {0}ms.", stopwatch.ElapsedMilliseconds));
#endif

                if (String.Compare(currentData, resendCommand) == 0)
                {
                    Console.WriteLine(String.Format("  Resend received. Time: {0}ms", stopwatch.ElapsedMilliseconds));
                    FileCopy.Common.Clipboard.SetText(command.ToString());
                }
                else
                {
                    // Ensure the clipboard still contains the command. Sometimes the clipboards get out of sync.
                    if (String.Compare(currentData, command.ToString()) != 0)
                    {
                        Console.WriteLine(String.Format("Setting clipboard again. Current: {0}. New: {1}",
                            String.IsNullOrEmpty(currentData) ? "<empty>" : currentData.Length < 50 ? currentData : String.Format("<length>{0}", currentData.Length),
                            command.ToString().Length < 50 ? command.ToString() : String.Format("<length>{0}", command.ToString().Length)));
                        FileCopy.Common.Clipboard.SetText(command.ToString());
                    }
                }

                System.Threading.Thread.Sleep(100);
            }
            while (true);
        }
    }
}